package main

import (
	"errors"
	"fmt"
	"github.com/gogf/gf/v2/container/gset"
	lua "github.com/yuin/gopher-lua"
	"iot-base/common/cache"
	"iot-base/common/logger"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

var g_mapLib sync.Map

func getDir() string {
	dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
	if !strings.HasSuffix(dir, `\`) && !strings.HasSuffix(dir, `/`) {
		dir = dir + "/ls/"
	}
	return dir
}
func reloadLua(path string) error {
	path = filepath.ToSlash(path)
	l, ok := g_mapLib.Load(path)
	logger.SLog.Info("lua reload begin-----------", path, ok)
	if ok {
		//logger.SLog.Info("lua reload begin----2-------", ok)
		linfo, okl := l.(*LuaLib)
		if okl {
			linfo.load(path)

		} else {
			logger.SLog.Error("lua reload failed--change type error---------", path)
		}
	}

	g_mapAlarm.Range(func(k, v interface{}) bool {
		g_mapAlarm.Delete(k)
		return true
	})

	return nil

}

type LuaLib struct {
	pLib   *lua.LState
	locker sync.Mutex
}

func (pLib *LuaLib) load(path string) error {
	pLib.locker.Lock()

	defer pLib.locker.Unlock()

	lib := lua.NewState()

	if errl := lib.DoFile(path); errl != nil {
		logger.SLog.Error("lua load failed", path, errl)
		if lib != nil {
			lib.Close()
		}
		return errl
	}
	if pLib.pLib != nil {
		pLib.pLib.Close()
	}

	logger.SLog.Info("lua reload succ-----------", path)
	pLib.pLib = lib
	return nil
}

const SPLIT = "::::"

func runLuaFunction(id string, fname string,
	mapParams map[string]string, pLua *LuaLib) (map[string]interface{}, error) {
	if pLua == nil {
		return nil, errors.New("lua lib load error")
	}
	pLua.locker.Lock()
	defer pLua.locker.Unlock()
	fmt.Println("------call----", fname)
	funDev := pLua.pLib.GetGlobal(fname)

	if funDev != nil {
		var ltable lua.LTable
		for k, v := range mapParams {
			ltable.RawSetString(k, lua.LString(v))
		}
		if err := pLua.pLib.CallByParam(lua.P{
			Fn:      funDev,
			NRet:    1,
			Protect: true}, lua.LString(id), &ltable); err != nil {
			logger.SLog.Error("lua call function failed ", fname, " err:", err)
			return nil, err
		}
		ret := pLua.pLib.Get(-1)
		defer pLua.pLib.Pop(1)
		if tbRet, ok := ret.(*lua.LTable); ok {
			result := make(map[string]interface{})
			pLua.pLib.ForEach(tbRet, func(k lua.LValue, v lua.LValue) {
				sk := k.String()
				result[sk] = v.String()
			})
			return result, nil
		}
		return nil, errors.New(ret.String())
	}

	//fmt.Println("error not found " + fname)
	logger.SLog.Error(id, " not found function ", fname)
	return nil, errors.New(id + " function not found " + fname)
}

func loadLibByDev(thing *cache.ThingInfo) (*LuaLib, error) {
	mapParams := thing.Params
	path := mapParams["lua"]
	if path == "" {
		return nil, errors.New("no path cfg------" + thing.Thing.UID)
	}
	dir := getDir()

	path = dir + path
	path = filepath.ToSlash(path)
	l, ok := g_mapLib.Load(path)

	if !ok {
		logger.SLog.Info("loader lua script ---reload-----", path, "found in map ----", ok)
		var libInfo LuaLib
		(&libInfo).load(path)
		g_mapLib.Store(path, &libInfo)
		l = &libInfo
	}
	linfo, okl := l.(*LuaLib)
	if !okl {
		logger.SLog.Error(thing.Thing.UID, " g_mapLib  get lua  failed  ", path)
		return nil, errors.New(thing.Thing.UID + " g_mapLib  get lua failed " + path)
	}
	return linfo, nil
}

func eachDev(id string, thing *cache.ThingInfo, dev interface{}, runCount int64) (interface{}, error) {
	fmt.Println("lua -------------each--------", id)
	linfo, err := loadLibByDev(thing)
	if err != nil {
		return nil, err
	}

	//ls := linfo.pLib
	if linfo != nil {

		mapData, errf := runLuaFunction(id, "getData", thing.Params, linfo)
		isOffline := handleData(id, errf, mapData)
		if isOffline {
			reportOfflineAlarm(id)
		} else {
			mapAlarm, _ := runLuaFunction(id, "getAlarm", thing.Params, linfo)

			if mapAlarm != nil {
				alarms, _ := g_mapAlarm.Load(id)
				curAlarms := gset.New(true)
				for aid, flag := range mapAlarm {
					if flag != "END" {
						curAlarms.Add(aid)
					}
				}
				g_mapAlarm.Store(id, curAlarms)
				if alarms == nil {
					syncAlarms(id, curAlarms)
				} else {
					//var oldAlarms *gset.Set
					oldAlarms := alarms.(*gset.Set)
					alarmClear := oldAlarms.Diff(curAlarms)
					alarmReport := curAlarms.Diff(oldAlarms)
					reportAlarm(id, "BEGIN", alarmReport)
					reportAlarm(id, "END", alarmClear)
					//reportAlarms(alarmReport)
				}
			}
		}

	}

	return nil, nil
}
